package controllers

import java.io.File
import akka.actor.{Actor, ActorSystem, PoisonPill, Props}
import akka.stream.Materializer
import akka.stream.scaladsl.Flow
import command.{CommandUtils, MissionUtils}
import dao._

import javax.inject.Inject
import models.Tables.WorkflowMissionRow
import org.joda.time.DateTime
import play.api.mvc._
import tool._
import utils.Utils

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import implicits.Implicits._
import org.zeroturnaround.zip.ZipUtil
import play.api.http.websocket.{BinaryMessage, CloseCodes, CloseMessage, Message, TextMessage}
import play.api.i18n.{I18nSupport, Lang}
import play.api.libs.streams.{ActorFlow, AkkaStreams}
import play.api.mvc.WebSocket.MessageFlowTransformer
import shared.Pojo._
import shared.SharedTool
import tool.Pojo._
import tool.workflow.AllWorkflow
import tool.workflow.fsSoft.AllFsSoft
import tool.workflow.soft.AllSubSoft

import java.nio.file.Files
import scala.language.postfixOps
import scala.concurrent.duration._
import upickle.default._

import scala.util.control.NonFatal

/** Created by yz on 27/10/2020
  */
class WorkflowController @Inject() (cc: ControllerComponents)(
    implicit val workflowMissionDao: WorkflowMissionDao,
    implicit val missionDao: MissionDao,
    implicit val modeDao: ModeDao,
    implicit val hmdbKeggInfoDao: HmdbKeggInfoDao,
    implicit val hmdbOtherNameKeggDao: HmdbOtherNameKeggDao,
    implicit val matrixFileDataDao: MatrixFileDataDao,
    implicit val system: ActorSystem,
    implicit val materializer: Materializer,
    implicit val keggInfoDao: KeggInfoDao
) extends AbstractController(cc)
    with WorkflowMissionFileTool
    with I18nSupport {

  import shared.pojo.Pojo.workflowMission._

  implicit val dao = MyDao(
    missionDao,
    modeDao,
    hmdbKeggInfoDao,
    hmdbOtherNameKeggDao,
    workflowMissionDao,
    matrixFileDataDao,
    keggInfoDao = keggInfoDao
  )

  implicit val enMessages = messagesApi.preferred(List(Lang("en")))

  def toIndex = Action { implicit request =>
    Ok(views.html.user.workflow.index())
  }

  def dataPreprocessBefore = Action { implicit request =>
    val kind = "dataPreprocess"
    val missionName = s"${kind}_${Tool.generateMissionName}"
    Ok(views.html.user.workflow.dataPreprocess(missionName, None))
  }

  def fs = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[FsData])
    val userId = Tool.getUserId
    val kind = "featureSelection"
    val input = List(data.file, data.groupFile)
    val row = WorkflowMissionRow(
      id = 0,
      missionName = s"${data.missionName}",
      kind = kind,
      userId = userId,
      startTime = new DateTime(),
      state = "preparing",
      input = input
    )
    workflowMissionDao.insert(row).flatMap(_ => workflowMissionDao.select(row.userId, kind, row.missionName)).flatMap {
      mission =>
        val outDir = getUserMissionKindDir(kind)
        val missionDir = MissionUtils.getMissionDir(mission.id, outDir)
        val (workspaceDir, resultDir) = (missionDir.workspaceDir, missionDir.resultDir)
        val rawDataFile = Tool.getMatrixFile(workspaceDir)
        Tool.produceRawMetaboliteFile(userId, data.file, rawDataFile)
        val outGroupFile = new File(workspaceDir, "group.txt")
        Tool.produceRawGroupFile(userId, data.groupFile, rawDataFile, outGroupFile)
        outGroupFile.copyTo(Tool.getRawGroupFile(workspaceDir))
        data.lines(";").toTxtFile(Tool.getCalculateConfigTxtFile(workspaceDir))
        Utils.t2ConfigFile(workspaceDir, data)
        val newMission = mission.copy(state = "wait")
        workflowMissionDao.update(newMission).map { x =>
          Ok(ujson.Obj("valid" -> true))
        }
    }
  }

  def updateMissionSocket = WebSocket.accept[ujson.Value, ujson.Value] { implicit request =>
    val userId = Tool.getUserId
    case class MissionAction(beforeMissions: Seq[WorkflowMissionRow], action: String)
    ActorFlow.actorRef(out =>
      Props(new Actor {
        override def receive: Receive = {
          case msg: ujson.Value if msg("info").str == "start" =>
            val beforeMissions = Utils.execFuture(workflowMissionDao.selectAll(userId))
            out ! Utils.getJsonByTs(beforeMissions)
            system.scheduler.scheduleOnce(3 seconds, self, MissionAction(beforeMissions, "update"))
          case MissionAction(beforeMissions, action) =>
            workflowMissionDao.selectAll(userId).map { missions =>
              val currentMissions = missions
              if (currentMissions.size != beforeMissions.size) {
                out ! Utils.getJsonByTs(currentMissions)
              } else {
                val b = currentMissions.zip(beforeMissions).forall { case (currentMission, beforeMission) =>
                  currentMission.id == beforeMission.id && currentMission.state == beforeMission.state
                }
                if (!b) {
                  out ! Utils.getJsonByTs(currentMissions)
                }
              }
              system.scheduler.scheduleOnce(3 seconds, self, MissionAction(currentMissions, "update"))
            }
          case x =>
            self ! PoisonPill
        }

        override def postStop(): Unit = {
          self ! PoisonPill
        }
      })
    )
  }

  def missionManageBefore = Action { implicit request =>
    Ok(views.html.user.workflowMission.manage())
  }

  def getAllMission = Action.async { implicit request =>
    val userId = Tool.getUserId
    workflowMissionDao.selectAll(userId).map { x =>
      Future {
        val missionIds = x.map(_.id.toString)
        val kinds = AllWorkflow.workflows.map(_.nameStr)
        kinds.foreach { kind =>
          val missionKindDir = getUserMissionKindDir(kind)
          missionKindDir
            .listFiles()
            .filter { dir =>
              !missionIds.contains(dir.getName)
            }
            .foreach(Utils.deleteDirectory(_))
        }
      }
      val array = Utils.getArrayByTsDefault(x)
      Ok(writeJs(array))
    }
  }

  def getLogContent = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { mission =>
      val missionIdDir = getMissionIdDir(Tool.getFileData(mission))
      val logFile = new File(missionIdDir, s"log.txt")
      val logStr = logFile.str
      Ok(logStr)
    }
  }

  def reRunMissionBefore = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { row =>
      val beforeMissionName = s"${row.missionName}"
      val r = "^.*_(\\d{1,4})$".r
      val missionName = if (beforeMissionName.matches(r.regex)) {
        "_(\\d{1,4})$".r.replaceAllIn(
          beforeMissionName,
          replacer => {
            s"_${(replacer.group(1).toInt + 1)}"
          }
        )
      } else s"${beforeMissionName}_1"
      row.kind match {
        case "dataPreprocess" => Ok(views.html.user.workflow.dataPreprocess(missionName, Some(data.missionId)))
        case "featureSelection" => Ok(views.html.user.workflow.featureSelection(missionName, Some(data.missionId)))
      }
    }
  }

  def viewHtml = Action.async { implicit request =>
    val path = "/assets/src/"
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { row =>
      val fileData = Tool.getFileData(row)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val data = FeatureSelectionTool.getFsData(workspaceDir)
      Ok(views.html.user.workflow.viewHtml(path, data.graphData, ""))
    }
  }

  def getConfigData = Action { implicit request =>
    val userId = Tool.getUserId
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val configFile = fileNameOp
      .map { fileName =>
        new File(Tool.exampleDir, fileName)
      }
      .getOrElse {
        val data = FormTool.missionIdForm.bindFromRequest().get
        val f = workflowMissionDao.select(data.missionId).map { row =>
          val fileData = Tool.getFileData(row)
          val workspaceDir = getMissionWorkspaceDir(fileData)
          Tool.getConfigFile(workspaceDir)
        }
        Utils.execFuture(f)
      }
    val json = Utils.configFile2JsonByFile(configFile)
    val finalJson = json
    Ok(json)
  }

  def configData = Action { implicit request =>
    val userId = Tool.getUserId
    val fileNameOp = FormTool.fileNameOpForm.bindFromRequest().get.fileName
    val configFile = fileNameOp
      .map { fileName =>
        new File(Tool.exampleDir, fileName)
      }
      .getOrElse {
        val data = FormTool.missionIdForm.bindFromRequest().get
        val f = workflowMissionDao.select(data.missionId).map { row =>
          val fileData = Tool.getFileData(row)
          val workspaceDir = getMissionWorkspaceDir(fileData)
          Tool.getConfigFile(workspaceDir)
        }
        Utils.execFuture(f)
      }
    val json = Utils.configFile2JsonByFile(configFile)
    val finalJson = json
    Ok(json)
  }

  def deleteMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val userId = Tool.getUserId
    workflowMissionDao.select(data.missionId).flatMap { mission =>
      Future {
        val fileData = Tool.getFileData(mission)
        val workspaceDir = getMissionWorkspaceDir(fileData)
        CommandUtils.killPid(workspaceDir)
        val missionIdDir = getMissionIdDir(fileData)
        Utils.deleteDirectory(missionIdDir)
      }
      workflowMissionDao.delete(userId, data.missionId).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def resultDetailBefore = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { row =>
      val kind = row.kind
      val curWorkflow = Tool.kindWorkflowMap(kind)
      val resultHtml = curWorkflow.getResultHtml(data.missionId)
      Ok(resultHtml)
    }
  }

  def getMissionById = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { x =>
      val json = writeJs(x)
      Ok(json)
    }
  }

  def getResultFileInfo = Action.async { implicit request =>
    val messages = request.messages
    val data = FormTool.missionIdForm.bindFromRequest().get
    workflowMissionDao.select(data.missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val resultDir = getMissionResultDir(fileData)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val value = Utils.configFile2Json(workspaceDir)
      val data = value.transform(reader[PreprocessSoftData])
      val files = resultDir.listFiles().filter { x =>
        x.getName.matches("^\\d+.*$")
      }
      val fileInfos = data.datas.map(_.kind).zipWithIndex.map { case (kind, i) =>
        val curSoft = AllSubSoft.kindSoftMap(kind)
        val file = files.find(_.getName.startsWith(s"${i + 1}.")).get
        MyFileInfoData(file.getName, messages(curSoft.nameStr), kind, messages(curSoft.nameStr))
      }
      val array = fileInfos.map { fileInfo =>
        val file = new File(resultDir, fileInfo.fileName)
        val rowNum = if (file.getName.endsWith("csv")) {
          file.csvLines.drop(1).size.toString
        } else "-"
        writeJs(fileInfo).toObj ++ ujson.Obj("rowNum" -> rowNum)
      }
      Ok(writeJs(array))
    }
  }

  def getFileData = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    workflowMissionDao.select(data.missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val file = new File(workspaceDir, fileName)
      val (columnNames, array) = file.nameSuffix match {
        case "xlsx" => {
          val lines = file.xlsxLinesWithColors()
          val columnNames = lines.head.map(_._1)
          val columnIndexs = columnNames.indices.map(_.toString)
          val array = lines.drop(1).map { columns =>
            columnIndexs
              .zip(columns)
              .map { case (index, (v, color)) =>
                (index -> ujson.Obj("color" -> s"${color}", "value" -> v))
              }
              .toMap
          }
          (columnNames, writeJs(array))
        }
        case _ => {
          val lines = file.csvLines
          val columnNames = lines.head
          val columnIndexs = columnNames.indices.map(_.toString)
          val array = lines.drop(1).map { columns =>
            columnIndexs.zip(columns).toMap
          }
          (columnNames, writeJs(array))
        }
      }
      val json = ujson.Obj("columnNames" -> columnNames, "array" -> array)
      Ok(json)
    }
  }

  def downloadResultFile = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    val fileName = FormTool.fileNameForm.bindFromRequest().get.fileName
    workflowMissionDao.select(missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val missionIdDir = getMissionIdDir(fileData)
      val workspaceDir = new File(missionIdDir, "workspace")
      val names = fileName.mySplit("/")
      val file = if (names.size > 1) {
        val outDir = new File(workspaceDir, names.head)
        new File(outDir, names.tail.mkString("/"))
      } else {
        new File(workspaceDir, fileName)
      }
      Ok.sendFile(file)
        .withHeaders(
          CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${file.getName}"),
          CONTENT_TYPE -> "application/x-download"
        )
    }
  }

  def downloadResult = Action.async { implicit request =>
    val userId = Tool.getUserId
    val data = FormTool.missionIdForm.bindFromRequest().get
    val missionId = data.missionId
    workflowMissionDao.select(missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val missionIdDir = getMissionIdDir(fileData)
      val resultFile = new File(missionIdDir, s"result.zip")
      val resultDir = new File(missionIdDir, "result")
      ZipUtil.pack(resultDir, resultFile)
      Ok.sendFile(resultFile)
        .withHeaders(
          CONTENT_DISPOSITION -> Tool.getContentDisposition(s"${mission.missionName}_result.zip"),
          CONTENT_TYPE -> "application/x-download"
        )
    }
  }

  def missionNameCheck = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[MissionNameCheck.Request])
    workflowMissionDao.selectOp(data.id, data.missionName).map { mission =>
      mission match {
        case Some(y) => Ok(ujson.Obj("valid" -> false))
        case None =>
          Ok(ujson.Obj("valid" -> true))
      }
    }
  }

  def updateName = Action.async { implicit request =>
    val missionName = FormTool.missionNameForm.bindFromRequest().get.missionName
    val missionId = FormTool.missionIdForm.bindFromRequest().get.missionId
    val userId = Tool.getUserId
    workflowMissionDao.select(missionId).flatMap { row =>
      val newRow = row.copy(missionName = missionName)
      workflowMissionDao.update(newRow).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def deletes = Action.async { implicit request =>
    val data = FormTool.idsForm.bindFromRequest().get
    val userId = Tool.getUserId
    workflowMissionDao.selectAll(data.ids).flatMap { missions =>
      Future {
        missions.foreach { mission =>
          val fileData = Tool.getFileData(mission)
          val workspaceDir = getMissionWorkspaceDir(fileData)
          CommandUtils.killPid(workspaceDir)
          val missionIdDir = getMissionIdDir(fileData)
          Utils.deleteDirectory(missionIdDir)
        }
      }
      workflowMissionDao.deletes(data.ids).map { x =>
        Ok(writeJs("success"))
      }
    }
  }

  def analysisBefore = Action { implicit request =>
    val kind = FormTool.kindForm.bindFromRequest().get.kind
    val curWorkflow = Tool.kindWorkflowMap(kind)
    val missionName = s"${kind}_${Tool.generateMissionName}"
    val html = curWorkflow.getHtml(missionName)
    Ok(html)
  }

  def getFsResultFileInfo = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val idStr = FormTool.idStrForm.bindFromRequest().get.idStr
    workflowMissionDao.select(data.missionId).map { mission =>
      val kind = SharedTool.getKind(idStr)
      val curSoft = AllFsSoft.kindSoftMap(kind)
      val fileData = Tool.getFileData(mission)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val index = SharedTool.getId(idStr)
      val fileInfos = curSoft.getFileInfos(workspaceDir, index)
      val array = fileInfos.map { fileInfo =>
        val subWorkspaceDir = new File(workspaceDir, idStr)
        val file = new File(subWorkspaceDir, s"${fileInfo.fileRelPath}/${fileInfo.fileName}")
        val rowNum = if (file.getName.endsWith("csv")) {
          file.csvLines.drop(1).size.toString
        } else "-"
        ujson.Obj(
          "fileName" -> fileInfo.fileName,
          "rowNum" -> rowNum,
          "intro" -> fileInfo.intro,
          "kind" -> fileInfo.kind,
          "fileRelPath" -> fileInfo.fileRelPath
        )
      }
      Ok(writeJs(array))
    }
  }

  def getDiffNum = Action.async { implicit request =>
    val data = FormTool.missionIdForm.bindFromRequest().get
    val idStrs = FormTool.idStrsForm.bindFromRequest().get.idStrs
    workflowMissionDao.select(data.missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val map = idStrs.map { idStr =>
        val kind = SharedTool.getKind(idStr)
        val subDir = new File(workspaceDir, idStr)
        val file = Tool.getDiffMetaboliteFile(subDir)
        val diffNum = if (file.exists()) {
          file.csvLines.drop(1).size
        } else 0
        (idStr, diffNum)
      }.toSeqMap
      Ok(writeJs(map))
    }
  }

  def fileExist = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[FileExistData])
    workflowMissionDao.select(data.missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val subWorkspaceDir = new File(workspaceDir, data.idStr)
      val file = new File(subWorkspaceDir, s"${data.fileName}")
      Ok(writeJs(file.exists()))
    }
  }

  def inputMetabolites = Action.async { implicit request =>
    val data = Tool.transformRq.transform(reader[InputMetabolitesRq])
    workflowMissionDao.select(data.missionId).map { mission =>
      val fileData = Tool.getFileData(mission)
      val workspaceDir = getMissionWorkspaceDir(fileData)
      val subWorkspaceDir = new File(workspaceDir, data.idStr)
      val file=Tool.getAllMetRawFile(subWorkspaceDir)
      val metabolites=file.txtLines.lineSeqMap.map(_("Metabolite"))
      Ok(writeJs(metabolites))
    }
  }

}
